/**
 * MIT License
 *
 * Copyright (c) 2024 小村程序员
 * Email: 397237738@qq.com
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.open.cobertura.core.provider;

import org.objectweb.asm.*;


/**
 * 在类上插桩__cobertura_init字段
 */
public class CoberturaClassFieldProbeArrayProvider {
    /**
     * 类名
     */
    private String className;
    private long classId;
    private ClassVisitor cv;
    private int probeCount=0;

    /**
     * 构造方法，需要传入一个ClassVisitor，用于对类进行插桩
     * @param cv
     */
    public CoberturaClassFieldProbeArrayProvider(ClassVisitor cv) {
        this.cv=cv;
    }

    /**
     * 添加探针成员变量
     * @param probeCount 探针的总数
     */
    public void addProbeMemberVar(final long classId,final String className,final int probeCount){
        this.className=className;
        this.classId=classId;
        createInitMethod(className,probeCount);
        addInitMemberVar();
        writeRuntimeCoberturaData();
    }
    /**
     * 创建init方法
     * 该方法将会创建一个cobertura_init()方法，并对__cobertura_init字段进行初始化
     *
     */
    private void createInitMethod(final String className,final int probeCount){
        //创建cobertura_init方法
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "__cobertura_counter", "()V", null, null);
        mv.visitCode();

        //访问字段
        Label label0 = new Label();
        mv.visitLabel(label0);
        //用静态调用指令，访问__cobertura_counter字段
        mv.visitFieldInsn(Opcodes.GETSTATIC, className, "__cobertura_counter", "[Z");
        Label label1 = new Label();
        //访问跳转指令，即当__cobertura_counter字段是空的时候，我们让程序跳出
        mv.visitJumpInsn(Opcodes.IFNONNULL, label1);

        //初始化__cobertura_init字段
        Label label2 = new Label();
        mv.visitLabel(label2);
        mv.visitIntInsn(Opcodes.BIPUSH, probeCount);//这个数字值需要跟据字节码指令来计算，暂未实现
        mv.visitIntInsn(Opcodes.NEWARRAY, Opcodes.T_BOOLEAN);
        mv.visitFieldInsn(Opcodes.PUTSTATIC, className, "__cobertura_counter", "[Z");
        mv.visitLabel(label1);
        mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(2, 0);
        mv.visitEnd();
    }

    /**
     * 该方法用于给类添加一个全局的成员变量，该字段是一个transient boolean[]类型的字段，字段名称为__cobertura_counter
     *
     */
    private void addInitMemberVar(){
        FieldVisitor fv = cv.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_TRANSIENT,
                "__cobertura_counter", "[Z", null, null);
        if (fv != null) {
            fv.visitEnd();
        }
    }

    /**
     * finalize方法的时效性不行，真正的agent应该使用命令接收，然后写入数据
     * @param className
     */
    private void writeCoberturaData(String className,final int probeCount){
        // 复写finalize方法
        MethodVisitor mv= cv.visitMethod(Opcodes.ACC_PROTECTED, "main", "([Ljava/lang/String;)V", null, null);
        if (mv == null) {
            mv = cv.visitMethod(Opcodes.ACC_PROTECTED, "main", "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
        }
        AnnotationVisitor ann = mv.visitAnnotation("Ljava/lang/Override;", true);
        ann.visitEnd();
        // 调用目标静态方法
        mv.visitLdcInsn(classId);
        mv.visitLdcInsn(className);
        mv.visitFieldInsn(Opcodes.GETSTATIC, className,"__cobertura_counter", "[Z");
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/cobertura/test/FileWriteUtil", "writeCoberturaData", "(JLjava/lang/String;[Z)V", false);
        mv.visitInsn(Opcodes.RETURN);
        mv.visitMaxs(Math.max(probeCount, 2), 0); // Maximum local stack size is 2
        mv.visitEnd();
    }

    private void writeRuntimeCoberturaData(){
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, "writeCoberturaProbeData", "(JLjava/lang/String;[Z)V", null, null);
        mv.visitCode();
        Label label0 = new Label();
        mv.visitLabel(label0);
        mv.visitLineNumber(55, label0);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "org/open/cobertura/core/data/CoberturaRuntimeStore", "getInstance", "()Lorg/open/cobertura/core/data/CoberturaRuntimeStore;", false);
        mv.visitVarInsn(Opcodes.LLOAD, 0);
        mv.visitVarInsn(Opcodes.ALOAD, 2);
        mv.visitVarInsn(Opcodes.ALOAD, 3);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/open/cobertura/core/data/CoberturaRuntimeStore", "store", "(JLjava/lang/String;[Z)V", false);
        Label label1 = new Label();
        mv.visitLabel(label1);
        mv.visitLineNumber(56, label1);
        mv.visitInsn(Opcodes.RETURN);
        Label label2 = new Label();
        mv.visitLabel(label2);
        mv.visitLocalVariable("id", "J", null, label0, label2, 0);
        mv.visitLocalVariable("className", "Ljava/lang/String;", null, label0, label2, 2);
        mv.visitLocalVariable("probeData", "[Z", null, label0, label2, 3);
        mv.visitMaxs(5, 4);
        mv.visitEnd();
    }
}